using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Hydra.Shell.Implementations;
using System.Reflection;
using HydraShellServer.Services;
using Hydra.Shell.Interfaces;
#if SILVERLIGHT
using SolutionFramework.Service;
using System.ServiceModel.DomainServices.Client;
using AbstraX;
using SilverlightDatabase;
using SilverlightDatabase.Exceptions;
using System.Windows;
using Utils;
using System.IO;
using System.Net;
using System.Diagnostics;
using System.Threading.Tasks;
using System.Windows.Threading;
using Hydra.Shell.Attributes;
using SolutionFramework.Entities;
using Hydra.Shell;
using System.Windows.Controls;
using OutputTree;
using SLControlLibrary;
using Hydra.Shell.Exceptions;
using System.Windows.Controls.Primitives;
#endif

namespace Hydra.Shell.Services
{
    public delegate void OnPackageAssemblyLoadedHandler(Guid packageGuid, Assembly assembly);
    public delegate void OnPackageAssemblyLoadFailedHandler(Guid packageGuid, Exception ex);
    public delegate void OnProtocolHandlerAssemblyLoadedHandler(Guid handlerGuid, Assembly assembly);
    public delegate void OnProtocolHandlerAssemblyLoadFailedHandler(Guid handlerGuid, Exception ex);
    public delegate void OnCorePackagesAvailableHandler(List<Exception> exceptions);

    public class HydraClientService : HydraShell
    {
#if SILVERLIGHT
        private HydraPackageContext hydraPackageContext;
        private IHydraUIInternals hydraUIInternals;
        private Queue<IHydraPackageListItem> packageQueue;
        private Queue<IProtocolHandlerListItem> handlerQueue;
        private Dictionary<Guid, IHydraPackageListItem> packagesListItems;
        private Dictionary<Guid, Assembly> packageAssemblies;
        private Dictionary<Guid, Assembly> handlerAssemblies;
        private Dictionary<Guid, HydraPackageWrapper> loadedPackages;
        private Dictionary<Guid, IHydraEditorListItem> editorsList;
        private Dictionary<Guid, HydraServiceWrapper> loadedServices;
        private Dictionary<Guid, HydraServiceListItem> serviceList;
        private Dictionary<Guid, IProtocolHandlerListItem> protocolHandlersList;
        private bool loading;
        public event OnCorePackagesAvailableHandler OnCorePackagesAvailable;
        private event EventHandler ServiceListReady;
        private event EventHandler ProtocolHandlersListReady;
        private event EventHandler EditorsListReady;
        private event OnPackageAssemblyLoadedHandler OnPackageAssemblyLoaded;
        private event OnPackageAssemblyLoadFailedHandler OnPackageAssemblyLoadFailed;  // todo - handle this
        private event OnProtocolHandlerAssemblyLoadedHandler OnProtocolHandlerAssemblyLoaded;
        private event OnProtocolHandlerAssemblyLoadFailedHandler OnProtocolHandlerAssemblyLoadFailed;  // todo - handle this

        public HydraClientService(IPackageAgent packageAgent, IHydraUIInternals hydraUIInternals, ISynchronousProxy synchronousProxy) : base(hydraUIInternals.MainPage, hydraUIInternals, packageAgent)
        {
            hydraPackageContext = new HydraPackageContext();
            synchronizationService = new HydraSynchronizerService(this, synchronousProxy);

            this.hydraUIInternals = hydraUIInternals;

            uiThread = new UIThread(hydraUIInternals.MainPage.Dispatcher);

            var packagesQuery = hydraPackageContext.GetPackageListQuery();
            var packageLoadOperation = hydraPackageContext.Load<HydraPackageListItem>(packagesQuery);

            packageLoadOperation.Completed += new EventHandler(PackageListLoadCompleted);

            var servicesQuery = hydraPackageContext.GetServicesQuery();
            var servicesLoadOperation = hydraPackageContext.Load<SolutionFramework.Entities.HydraService>(servicesQuery);

            servicesLoadOperation.Completed += new EventHandler(ServicesLoadOperationCompleted);

            var handlersQuery = hydraPackageContext.GetProtocolHandlerListQuery();
            var handlersLoadOperation = hydraPackageContext.Load<ProtocolHandlerListItem>(handlersQuery);

            handlersLoadOperation.Completed += new EventHandler(HandlersLoadOperationCompleted);

            var editorsQuery = hydraPackageContext.GetEditorListQuery();
            var editorsLoadOperation = hydraPackageContext.Load<HydraEditorListItem>(editorsQuery);

            editorsLoadOperation.Completed += new EventHandler(EditorsLoadOperationCompleted);

            var coreCheckoffList = new List<Guid>(this.CorePackageGuids);
            var exceptions = new List<Exception>();
            var serviceListReady = false;
            var corePackagesAvailable = false;
            var corePackagesAvailableRaised = false;

            this.OnCorePackagesAvailable += (exceptions2) =>
            {
                Debug.WriteLine(">>>>>>>>>>>>>>>>>>> Siting ModelTreeView");

                var root = App.Current.RootVisual;

                root.GetVisualDescendantsAndSelf().OfType<IObjectWithSite>().Distinct().ToList().ForEach(o =>
                {
                    o.SetSite(this);
                });
            };

            Debug.WriteLine(">>>>>>>>>>>>>>>>>>> Loading packages");

            this.OnPackageAssemblyLoaded += (packageGuid, assembly) =>
            {
                Debug.WriteLine(string.Format(">>>>>>>>>>>>>>>>>>> Package assembly {{{0}}} loaded as {1}", packageGuid, new FileInfo(assembly.ManifestModule.ToString()).Name));

                ProviderClientManager.RaiseOnGeneralMessage(string.Format("Package assembly {{{0}}} loaded as {1}", packageGuid, new FileInfo(assembly.ManifestModule.ToString()).Name));

                if (!corePackagesAvailableRaised && coreCheckoffList.Contains(packageGuid))
                {
                    coreCheckoffList.Remove(packageGuid);

                    Debug.WriteLine(string.Format(">>>>>>>>>>>>>>>>>>> {0} packages remaining", coreCheckoffList.Count));

                    if (coreCheckoffList.Count == 0)
                    {
                        if (serviceListReady)
                        {
                            OnCorePackagesAvailable(exceptions);
                            corePackagesAvailableRaised = true;
                        }

                        corePackagesAvailable = true;
                    }
                }
            };

            this.OnPackageAssemblyLoadFailed += (packageGuid, ex) =>
            {
                coreCheckoffList.Remove(packageGuid);
                ProviderClientManager.RaiseOnInternalError(ex);

                exceptions.Add(ex);

                if (coreCheckoffList.Count == 0)
                {
                    OnCorePackagesAvailable(exceptions);
                }
            };

            ServiceListReady += (sender, e) =>
            {
                if (corePackagesAvailable)
                {
                    OnCorePackagesAvailable(exceptions);
                }

                serviceListReady = true;
            };
        }

        private void EditorsLoadOperationCompleted(object sender, EventArgs e)
        {
            var loadOperation = (LoadOperation<HydraEditorListItem>)sender;

            if (loadOperation.HasError)
            {
                throw loadOperation.Error;
            }
            else
            {
                LoadEditorsList(loadOperation.Entities);
            }
        }

        private void LoadEditorsList(IEnumerable<HydraEditorListItem> editors)
        {
            foreach (var editor in editors)
            {
                this.EditorsList.Add(editor.EditorID, new Hydra.Shell.Implementations.HydraEditorListItem
                {
                    EditorID = editor.EditorID,
                    EditorName = editor.EditorName,
                    PackageGuid = editor.PackageGuid,
                    Extensions = editor.Extensions
                });
            }

            if (EditorsListReady != null)
            {
                EditorsListReady(this, EventArgs.Empty);
            }
        }
        private void HandlersLoadOperationCompleted(object sender, EventArgs e)
        {
            var loadOperation = (LoadOperation<ProtocolHandlerListItem>)sender;

            if (loadOperation.HasError)
            {
                throw loadOperation.Error;
            }
            else
            {
                LoadProtocolHandlersList(loadOperation.Entities);
            }
        }

        private void LoadProtocolHandlersList(IEnumerable<ProtocolHandlerListItem> handlers)
        {
            foreach (var handler in handlers)
            {
                this.ProtocolHandlersList.Add(handler.HandlerGuid, new Hydra.Shell.Implementations.ProtocolHandlerListItem
                {
                    HandlerGuid = handler.HandlerGuid,
                    Scheme = handler.Scheme,
                    FileName = handler.FileName,
                    ModifiedDate = handler.ModifiedDate,
                    Cached = handler.Cached
                });
            }

            if (ProtocolHandlersListReady != null)
            {
                ProtocolHandlersListReady(this, EventArgs.Empty);
            }
        }

        public override UserControl MainPage
        {
            get
            {
                return hydraUIInternals.MainPage;
            }
        }

        public override bool ServiceAvailableButNotLoaded(Guid guidService)
        {
            return this.ServiceList.ContainsKey(guidService);
        }

        private void ServicesLoadOperationCompleted(object sender, EventArgs e)
        {
            var loadOperation = (LoadOperation<SolutionFramework.Entities.HydraService>)sender;

            if (loadOperation.HasError)
            {
                throw loadOperation.Error;
            }
            else
            {
                LoadServiceList(loadOperation.Entities);
            }
        }

        private void LoadServiceList(IEnumerable<SolutionFramework.Entities.HydraService> services)
        {
            foreach (var service in services)
            {
                this.ServiceList.Add(service.ServiceGuid, new HydraServiceListItem
                {
                    ServiceID = service.ServiceID,
                    ServiceGuid = service.ServiceGuid,
                    PackageGuid = service.PackageGuid,
                    ServiceName = service.ServiceName
                });
            }

            ServiceListReady(this, EventArgs.Empty);
        }

        private void PackageListLoadCompleted(object sender, EventArgs e)
        {
            var loadOperation = (LoadOperation<HydraPackageListItem>)sender;

            if (loadOperation.HasError)
            {
                throw loadOperation.Error;
            }
            else
            {
                loadOperation.Entities.ToList().ForEach(entity =>
                {
                    this.PackageList.Add(entity.PackageGuid, new Hydra.Shell.Implementations.HydraPackageListItem
                    {
                        PackageGuid = entity.PackageGuid,
                        FileName = entity.FileName,
                        ModifiedDate = entity.ModifiedDate
                    });
                });

                LoadCorePackages(this.PackageList.Values);
            }
        }

        public List<Guid> CorePackageGuids
        {
            get
            {
                var list = new List<Guid>()
                {
                    new Guid("da9fb551-c724-11d0-ae1f-00a0c90fffc3"),   // Environment
                    new Guid("ab2a817c-42d0-45d5-adbd-2a0c359b2c77")    // DTE
                };

                return list;
            }
        }

        private void LoadCorePackages(IEnumerable<IHydraPackageListItem> packages)
        {
            var corePackages = packages.Where(p => this.CorePackageGuids.Any(g => g == p.PackageGuid));

            packageAgent.SendGeneralMessage("Loading core packages");

            LoadPackages(corePackages);
        }

        protected override Dictionary<Guid, HydraServiceListItem> ServiceList
        {
            get
            {
                if (serviceList == null)
                {
                    serviceList = new Dictionary<Guid, HydraServiceListItem>();
                }

                return serviceList;
            }
        }

        public override Dictionary<Guid, IProtocolHandlerListItem> ProtocolHandlersList
        {
            get 
            {
                if (protocolHandlersList == null)
                {
                    protocolHandlersList = new Dictionary<Guid, IProtocolHandlerListItem>();
                }

                return protocolHandlersList;
            }
        }

        protected override Dictionary<Guid, Assembly> PackageAssemblies
        {
            get 
            {
                if (packageAssemblies == null)
                {
                    packageAssemblies = new Dictionary<Guid, Assembly>();
                }

                return packageAssemblies;
            }
        }

        protected override Dictionary<Guid, Assembly> ProtocolHandlerAssemblies
        {
            get
            {
                if (handlerAssemblies == null)
                {
                    handlerAssemblies = new Dictionary<Guid, Assembly>();
                }

                return handlerAssemblies;
            }
        }

        public override Dictionary<Guid, HydraPackageWrapper> LoadedPackages
        {
            get 
            {
                if (loadedPackages == null)
                {
                    loadedPackages = new Dictionary<Guid, HydraPackageWrapper>();
                }

                return loadedPackages;
            }
        }

        public override Dictionary<Guid, IHydraEditorListItem> EditorsList
        {
            get
            {
                if (editorsList == null)
                {
                    editorsList = new Dictionary<Guid, IHydraEditorListItem>();
                }

                return editorsList;
            }
        }

        public override Dictionary<Guid, HydraServiceWrapper> LoadedServices
        {
            get
            {
                if (loadedServices == null)
                {
                    loadedServices = new Dictionary<Guid, HydraServiceWrapper>();
                }

                return loadedServices;
            }
        }

        public override Dictionary<Guid, IHydraPackageListItem> PackageList
        {
            get
            {
                if (packagesListItems == null)
                {
                    packagesListItems = new Dictionary<Guid, IHydraPackageListItem>();
                }

                return packagesListItems;
            }
        }

        private void OnOpenException(OpenException exception, ref bool deleteAndRecreate)
        {
            if (MessageBox.Show("Client database is corrupted.  Would you like to recreate?", "Client database", MessageBoxButton.OKCancel) == MessageBoxResult.OK)
            {
                deleteAndRecreate = true;
            }
        }

        public void LoadPackages(IEnumerable<IHydraPackageListItem> packages)
        {
            var packageList = packages.Select(p => p.PackageGuid.ToString()).ToCommaDelimitedList();

            if (packageQueue == null)
            {
                packageQueue = new Queue<IHydraPackageListItem>();
            }

            LogInfo("~~~~~~~~~ Loading packages: " + packageList);

            if (ProviderClientManager.EnableCaching)
            {
                ProviderClientManager.NameThread("LoadPackages: " + packageList);

                foreach (var package in packages)
                {
                    var hydraPackageListItem = new HydraPackageListItemLocal
                    {
                        PackageGuid = package.PackageGuid,
                        FileName = package.FileName
                    };

                    try
                    {

                        packageAgent.SendPackageMessage(hydraPackageListItem, PackageMessageType.Loading);

                        using (var session = DatabaseSession.Open(ProviderClientManager.DATABASE_NAME, ProviderClientManager.Log, OnOpenException))
                        {
                            var fileTable = session.GetTable<ProviderClientManager.SavedFile<HydraPackageListItemLocal>>();
                            var packagesLeft = packages.Count();

                            if (fileTable.Any(s => s.FileName == package.FileName))
                            {
                                ProviderClientManager.RaiseOnGeneralMessage(string.Format("Package '{0}' found in table.", package.FileName));

                                packageAgent.SendPackageMessage(hydraPackageListItem, PackageMessageType.FoundInCache);

                                var item = fileTable.Single(s => s.FileName == package.FileName);
                                var packageListItem = (HydraPackageListItemLocal)item.Extra;
                                var modifiedDate = package.ModifiedDate;

                                if (modifiedDate < item.LastSaved)
                                {
                                    var stream = item.Data.ToMemory();

                                    // Log.InfoFormat("Package '{0}' server modified {1:MM/dd/yyyy hh:mm:ss} less than last saved {2:MM/dd/yyyy hh:mm:ss}.  Loading from cache.", package.FileName, modifiedDate, item.LastSaved);

                                    package.Cached = true;

                                    packageAgent.SendPackageMessage(hydraPackageListItem, PackageMessageType.LoadingFromCache);
                                    LoadPackageAssembly(stream);
                                }
                                else
                                {
                                    packageAgent.SendPackageMessage(hydraPackageListItem, PackageMessageType.CacheOutdated);

                                    // Log.InfoFormat("Package '{0}' server modified {1:MM/dd/yyyy hh:mm:ss} greater than last saved {2:MM/dd/yyyy hh:mm:ss}.  Removing from database and adding to queue.", package.FileName, modifiedDate, item.LastSaved);

                                    fileTable.Remove(item);

                                    packageQueue.Enqueue(package);
                                }
                            }
                            else
                            {
                                // Log.InfoFormat("Package '{0}' not found in database.  Adding to queue.", package.FileName);

                                packageQueue.Enqueue(package);
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        packageAgent.SendPackageError(hydraPackageListItem, ex);
                    
                        ProviderClientManager.RaiseOnGeneralMessage(string.Format("Error loading package '{0}'. ", ex.Message));
                        Debugger.Break();
                    }
                }
            }
            else
            {
                foreach (var package in packages.Where(p1 => !packageQueue.Any(p2 => p1 == p2)))
                {
                    packageQueue.Enqueue(package);
                }
            }

            if (packageQueue.Count > 0)
            {
                var timer = new DispatcherTimer();

                timer.Interval = new TimeSpan(0, 0, 0, 0, 1);

                timer.Tick += (object s, EventArgs args) =>
                {
                    if (!loading)
                    {
                        var client = new WebClient();
                        var package = packageQueue.Dequeue();

                        loading = true;

                        try
                        {
                            var hydraPackageListItem = new HydraPackageListItemLocal
                            {
                                PackageGuid = package.PackageGuid,
                                FileName = package.FileName
                            };

                            packageAgent.SendPackageMessage(hydraPackageListItem, PackageMessageType.Downloading);

                            LogInfo("~~~~~~~~~ Downloading " + Path.GetFileNameWithoutExtension(package.FileName));

                            client.OpenReadCompleted += new OpenReadCompletedEventHandler(OnPackageAssemblyOpened);
                            client.OpenReadAsync(new Uri(package.FileName, UriKind.Relative));

                            if (packageQueue.Count == 0)
                            {
                                timer.Stop();
                            }
                        }
                        catch (Exception ex)
                        {
                            loading = false;
                            OnPackageAssemblyLoadFailed(package.PackageGuid, ex);
                        }
                    }
                };

                timer.Start();
            }
        }

        private void LogInfo(string info)
        {
            Debug.WriteLine(info);

            this.Log.Info(info);
            ProviderClientManager.SetStatus(info);
        }

        private void OnPackageAssemblyOpened(object sender, OpenReadCompletedEventArgs e)
        {
            var assemblyStream = e.Result.ToMemory();

            try
            {
                LoadPackageAssembly(assemblyStream);
            }
            catch (Exception ex)
            {
                //OnPackageAssemblyLoadFailed(package.PackageGuid, ex);
            }
        }

        private void LoadPackageAssembly(MemoryStream assemblyStream)
        {
            var packageGuid = Guid.Empty;

            try
            {
                var assemblyPart = new AssemblyPart();
                var loadStream = assemblyStream.ToMemory();
                var assembly = assemblyPart.Load(loadStream);
                var packageObjectFound = false;
                var name = assembly.ManifestModule.ToString();
                var item = packagesListItems.Values.Single(p => p.FileName == name);

                var hydraPackageListItem = new HydraPackageListItemLocal
                {
                    PackageGuid = item.PackageGuid,
                    FileName = item.FileName
                };

                LogInfo("~~~~~~~~~ Downloaded: " + item.FileName);

                packageGuid = item.PackageGuid;

                foreach (Type type in assembly.GetTypes())
                {
                    var interfaces = type.GetInterfaces().AsQueryable().Cast<Type>();

                    if (interfaces.Any(t => t.Name == "IHydraPackage"))
                    {
                        var guidAttribute = (GuidAttribute)type.GetCustomAttributes(true).Single(a => a is GuidAttribute);
                        var guid = Guid.Parse(guidAttribute.Value);

                        packageAgent.SendPackageMessage(hydraPackageListItem, PackageMessageType.Downloaded);

                        PackageAssemblies.Add(guid, assembly);

                        uiThread.Run(() =>
                        {
                            OnPackageAssemblyLoaded(guid, assembly);
                        });

                        packageObjectFound = true;
                        break;
                    }
                }

                if (!packageObjectFound)
                {
                    Debugger.Break();
                }

                if (ProviderClientManager.EnableCaching)
                {
                    if (!item.Cached)
                    {
                        CachePackageAssembly(assemblyStream, name, item);
                    }
                }
            }
            catch (Exception ex)
            {
                packageAgent.SendGeneralError(ex);
                OnPackageAssemblyLoadFailed(packageGuid, ex);
            }

            loading = false;
        }

        private void CachePackageAssembly(MemoryStream assemblyStream, string name, IHydraPackageListItem item)
        {
            assemblyStream.Seek(0, SeekOrigin.Begin);

            var savedFile = new ProviderClientManager.SavedFile<HydraPackageListItemLocal>
            {
                FileName = name,
                Extra = new HydraPackageListItemLocal
                {
                    FileName = item.FileName,
                    ModifiedDate = item.ModifiedDate,
                    PackageGuid = item.PackageGuid,
                    ID = item.PackageGuid.ToString()
                },
                Data = assemblyStream.ToArray(),
                LastSaved = DateTime.UtcNow
            };

            packageAgent.SendPackageMessage(savedFile.Extra, Interfaces.PackageMessageType.Caching);

            // Log.InfoFormat("Package '{0}' being added to database. Save date {1:MM/dd/yyyy hh:mm:ss}", name, savedFile.LastSaved);

            assemblyStream.Close();

            var task = new Task(() =>
            {
                try
                {
                    ProviderClientManager.NameThread("CacheAssembly");

                    using (var session = DatabaseSession.Open(ProviderClientManager.DATABASE_NAME, ProviderClientManager.Log, OnOpenException))
                    {
                        var fileTable = session.GetTable<ProviderClientManager.SavedFile<HydraPackageListItemLocal>>();

                        if (fileTable.Any(s => s.FileName == savedFile.FileName))
                        {
                            Debugger.Break();
                        }
                        else
                        {
                            fileTable.Add(savedFile);

                            this.uiThread.Run(() =>
                            {
                                packageAgent.SendPackageMessage(savedFile.Extra, Interfaces.PackageMessageType.Cached);
                            });
                        }
                    }
                }
                catch (Exception ex)
                {
                    throw;
                }
            });

            task.Start();
        }

        protected override ServiceLoadOperation<T> LoadService<T>(Guid guidService)
        {
            var serviceLoadOperation = new ServiceLoadOperation<T>(uiThread);

            if (this.ServiceList.ContainsKey(guidService))
            {
                var serviceListItem = this.ServiceList[guidService];

                if (this.LoadedPackages.ContainsKey(serviceListItem.PackageGuid))
                {
                    FindService<T>(guidService); 
                }
                else if (this.PackageList.ContainsKey(serviceListItem.PackageGuid))
                {
                    this.OnPackageAssemblyLoaded += (packageGuid, assembly) =>
                    {
                        if (packageGuid == serviceListItem.PackageGuid)
                        {
                            var service = FindService<T>(guidService);

                            serviceLoadOperation.DelaySetLoaded(service);
                        }
                    };

                    this.OnPackageAssemblyLoadFailed += (packageGuid, ex) =>
                    {
                        if (packageGuid == serviceListItem.PackageGuid)
                        {
                            serviceLoadOperation.DelaySetException(new Exception(string.Format("Service load of {{{0}}} failed. View InnerException", guidService), ex));
                        }
                    };

                    LoadPackages(new List<IHydraPackageListItem> { this.PackageList[serviceListItem.PackageGuid] });
                }
                else
                {
                    serviceLoadOperation.DelaySetException(new Exception(string.Format("Package {{{0}}} for service {{{1}}} not installed.", serviceListItem.PackageGuid, guidService)));
                }
            }
            else
            {
                serviceLoadOperation.DelaySetException(new Exception(string.Format("Service {{{0}}} not installed.", guidService)));
            }

            return serviceLoadOperation;
        }

        protected override ServiceLoadOperation LoadService(Guid guidService, Guid guidInterFace)
        {
            var serviceLoadOperation = new ServiceLoadOperation(uiThread);

            packageAgent.SendGeneralMessage(string.Format("Request to load service {{{0}}} for interface {{{1}}}", guidService, guidInterFace));

            if (this.ServiceList.ContainsKey(guidService))
            {
                var serviceListItem = this.ServiceList[guidService];

                if (this.LoadedPackages.ContainsKey(serviceListItem.PackageGuid))
                {
                    var package = this.LoadedPackages[serviceListItem.PackageGuid];

                    var service = FindService(guidService, guidInterFace);

                    serviceLoadOperation.DelaySetLoaded(service);
                }
                else if (this.PackageList.ContainsKey(serviceListItem.PackageGuid))
                {
                    var packageListItem = this.PackageList[serviceListItem.PackageGuid];
                    var packageListItemLocal = new HydraPackageListItemLocal
                    {
                        PackageGuid = packageListItem.PackageGuid,
                        FileName = packageListItem.FileName
                    };

                    packageAgent.SendPackageMessage(packageListItemLocal, Interfaces.PackageMessageType.LoadingService, "{" + guidService.ToString() + "}");

                    LoadPackages(new List<IHydraPackageListItem> { this.PackageList[serviceListItem.PackageGuid] });

                    this.OnPackageAssemblyLoaded += (packageGuid, assembly) =>
                    {
                        if (packageGuid == serviceListItem.PackageGuid)
                        {
                            try
                            {
                                var service = FindService(guidService, guidInterFace);

                                serviceLoadOperation.DelaySetLoaded(service);
                            }
                            catch (ServiceAvailableButRequiresDependenciesException ex)
                            {
                                var constructor = ex.Constructor;
                                var dependencies = ex.Dependencies;
                                var serviceProvider = ex.QueryServiceProvider;
                                var parameters = new object[dependencies.Count + 1];
                                var dependencyOps = new Dictionary<int, ServiceLoadOperation>();
                                var index = 1;
                                var serviceLoadOperation2 = serviceLoadOperation;

                                foreach (var type in dependencies)
                                {
                                    ServiceLoadOperation serviceLoadOp;

                                    if (serviceProvider.QueryService(type.GetGuid(), type.GetGuid(), out serviceLoadOp))
                                    {
                                        var thisIndex = index;

                                        dependencyOps.Add(index, serviceLoadOp);

                                        serviceLoadOp.ExceptionOccured += (sender, e) =>
                                        {
                                            Debugger.Break();
                                        };

                                        serviceLoadOp.ServiceLoaded += (sender, e) =>
                                        {
                                            parameters[thisIndex] = e.Service;
                                            dependencyOps.Remove(thisIndex);

                                            if (dependencyOps.Count == 0)
                                            {
                                                parameters[0] = uiThread;

                                                var service = (IUnknown)constructor.Invoke(parameters);
                                                var interfaceObject = (IUnknown)null;

                                                if (service.QueryInterface(guidInterFace, out interfaceObject))
                                                {
                                                    serviceLoadOperation2.DelaySetLoaded(interfaceObject);
                                                }
                                            }
                                        };
                                    }

                                    index++;
                                }
                            }
                            catch (Exception ex)
                            {
                                serviceLoadOperation.DelaySetException(ex);
                            }
                        }
                    };

                    this.OnPackageAssemblyLoadFailed += (packageGuid, ex) =>
                    {
                        if (packageGuid == serviceListItem.PackageGuid)
                        {
                            serviceLoadOperation.DelaySetException(new Exception(string.Format("Service load of {{{0}}} failed. View InnerException", guidService), ex));
                        }
                    };
                }
                else
                {
                    var exception = new Exception(string.Format("Package {{{0}}} for service {{{0}}} not installed.", serviceListItem.PackageGuid, guidService));
                    var packageListItem = this.PackageList[serviceListItem.PackageGuid];
                    var packageListItemLocal = new HydraPackageListItemLocal
                    {
                        PackageGuid = packageListItem.PackageGuid,
                        FileName = packageListItem.FileName
                    };

                    packageAgent.SendPackageError(packageListItemLocal, exception);

                    serviceLoadOperation.DelaySetException(exception);
                }
            }
            else
            {
                var exception = new Exception(string.Format("Service {{{0}}} not installed.", guidService));

                packageAgent.SendGeneralError(exception);

                serviceLoadOperation.DelaySetException(exception);
            }

            return serviceLoadOperation;
        }

        public override PackageLoadOperation LoadPackage(Guid guidPackage)
        {
            var operation = new PackageLoadOperation(uiThread);

            if (this.PackageList.ContainsKey(guidPackage))
            {
                var packageListItem = this.PackageList[guidPackage];

                if (this.PackageAssemblies.ContainsKey(guidPackage))
                {
                    var package = FindPackage(guidPackage);

                    operation.DelaySetLoaded(package);
                }
                else 
                {
                    uiThread.Run(() =>
                    {
                        LoadPackages(new List<IHydraPackageListItem> { this.PackageList[packageListItem.PackageGuid] });
                    });

                    this.OnPackageAssemblyLoaded += (packageGuid, assembly) =>
                    {
                        if (packageGuid == packageListItem.PackageGuid)
                        {
                            var package = FindPackage(guidPackage);

                            operation.DelaySetLoaded(package);
                        }
                    };
                     
                    this.OnPackageAssemblyLoadFailed += (packageGuid, ex) =>
                    {
                        if (packageGuid == packageListItem.PackageGuid)
                        {
                            operation.DelaySetException(new Exception(string.Format("Load of package {{{0}}} failed. View InnerException", guidPackage), ex));
                        }
                    }; 
                }
            }
            else
            {
                operation.DelaySetException(new Exception(string.Format("Package {{{0}}} not installed.", guidPackage)));
            }

            return operation;
        }

        public void LoadProtocolHandlers(IEnumerable<IProtocolHandlerListItem> handlers)
        {
            var handlersList = handlers.Select(h => h.HandlerGuid.ToString()).ToCommaDelimitedList();

            if (handlerQueue == null)
            {
                handlerQueue = new Queue<IProtocolHandlerListItem>();
            }

            LogInfo("Loading protocol handlers: " + handlersList);

            if (ProviderClientManager.EnableCaching)
            {
                try
                {
                    ProviderClientManager.NameThread("LoadHandlers: " + handlersList);

                    foreach (var handler in handlers)
                    {
                        using (var session = DatabaseSession.Open(ProviderClientManager.DATABASE_NAME, ProviderClientManager.Log, OnOpenException))
                        {
                            var fileTable = session.GetTable<ProviderClientManager.SavedFile<ProtocolHandlerListItemLocal>>();
                            var packagesLeft = handlers.Count();

                            if (fileTable.Any(s => s.FileName == handler.FileName))
                            {
                                ProviderClientManager.RaiseOnGeneralMessage(string.Format("Package '{0}' found in table.", handler.FileName));

                                var item = fileTable.Single(s => s.FileName == handler.FileName);
                                var handlerListItem = (ProtocolHandlerListItemLocal)item.Extra;
                                var modifiedDate = handler.ModifiedDate;

                                if (modifiedDate < item.LastSaved)
                                {
                                    var stream = item.Data.ToMemory();

                                    // Log.InfoFormat("Package '{0}' server modified {1:MM/dd/yyyy hh:mm:ss} less than last saved {2:MM/dd/yyyy hh:mm:ss}.  Loading from cache.", package.FileName, modifiedDate, item.LastSaved);

                                    handler.Cached = true;

                                    LoadHandlerAssembly(stream);
                                }
                                else
                                {
                                    // Log.InfoFormat("Package '{0}' server modified {1:MM/dd/yyyy hh:mm:ss} greater than last saved {2:MM/dd/yyyy hh:mm:ss}.  Removing from database and adding to queue.", package.FileName, modifiedDate, item.LastSaved);

                                    fileTable.Remove(item);

                                    handlerQueue.Enqueue(handler);
                                }
                            }
                            else
                            {
                                // Log.InfoFormat("Package '{0}' not found in database.  Adding to queue.", package.FileName);

                                handlerQueue.Enqueue(handler);
                            }
                        }
                    }
                }
                catch (Exception ex)
                {
                    throw;
                }
            }
            else
            {
                foreach (var handler in handlers.Where(h1 => !handlerQueue.Any(h2 => h1 == h2)))
                {
                    handlerQueue.Enqueue(handler);
                }
            }

            if (handlerQueue.Count > 0)
            {
                var timer = new DispatcherTimer();

                timer.Interval = new TimeSpan(0, 0, 0, 0, 1);

                timer.Tick += (object s, EventArgs args) =>
                {
                    if (!loading)
                    {
                        var client = new WebClient();
                        var handler = handlerQueue.Dequeue();

                        loading = true;

                        try
                        {
                            LogInfo("Loading " + Path.GetFileNameWithoutExtension(handler.FileName));

                            client.OpenReadCompleted += new OpenReadCompletedEventHandler(OnHandlerAssemblyOpened);
                            client.OpenReadAsync(new Uri(handler.FileName, UriKind.Relative));

                            if (handlerQueue.Count == 0)
                            {
                                timer.Stop();
                            }
                        }
                        catch (Exception ex)
                        {
                            loading = false;
                            OnProtocolHandlerAssemblyLoadFailed(handler.HandlerGuid, ex);
                        }
                    }
                };

                timer.Start();
            }
        }

        private void OnHandlerAssemblyOpened(object sender, OpenReadCompletedEventArgs e)
        {
            var assemblyStream = e.Result.ToMemory();

            try
            {
                LoadHandlerAssembly(assemblyStream);
            }
            catch (Exception ex)
            {
                //OnPackageAssemblyLoadFailed(package.PackageGuid, ex);
            }
        }

        private IUnknown GetProtocolHandler(Guid guidHandler, Assembly assembly)
        {
            var handlerGuid = Guid.Empty;

            try
            {
                var packageObjectFound = false;
                var name = assembly.ManifestModule.ToString();
                var item = protocolHandlersList.Values.Single(p => p.FileName == name);

                handlerGuid = item.HandlerGuid;

                foreach (Type type in assembly.GetTypes())
                {
                    var interfaces = type.GetInterfaces().AsQueryable().Cast<Type>();

                    if (interfaces.Any(t => t.Name == "IUnknown" && type.HasCustomAttribute<GuidAttribute>()))
                    {
                        var guidAttribute = type.GetCustomAttribute<GuidAttribute>();
                        var guid = Guid.Parse(guidAttribute.Value);

                        if (guid == guidHandler)
                        {
                            var handler = (IUnknown)Activator.CreateInstance(type, new object[] { uiThread });

                            return handler;
                        }
                    }
                }

                if (!packageObjectFound)
                {
                    Debugger.Break();
                }
            }
            catch (Exception ex)
            {
                Debugger.Break();
            }

            return null;
        }

        public override HandlerLoadOperation LoadProtocolHandler(Guid guidHandler)
        {
            OnProtocolHandlerAssemblyLoadedHandler loadedHandler = null;
            OnProtocolHandlerAssemblyLoadFailedHandler failedHandler = null;
            var loadOperation = new HandlerLoadOperation(this.uiThread);

            loadedHandler = (sender, e) =>
            {
                var handler = GetProtocolHandler(guidHandler, e);

                loadOperation.SetLoaded(handler);

                OnProtocolHandlerAssemblyLoaded -= loadedHandler;
            };

            failedHandler = (sender, e) =>
            {

                OnProtocolHandlerAssemblyLoadFailed -= failedHandler;
            };

            OnProtocolHandlerAssemblyLoaded += loadedHandler;
            OnProtocolHandlerAssemblyLoadFailed += failedHandler;

            LoadProtocolHandlers(new List<IProtocolHandlerListItem> { this.ProtocolHandlersList[guidHandler] });

            return loadOperation;
        }

        private void LoadHandlerAssembly(MemoryStream assemblyStream)
        {
            var handlerGuid = Guid.Empty;

            try
            {
                var assemblyPart = new AssemblyPart();
                var loadStream = assemblyStream.ToMemory();
                var assembly = assemblyPart.Load(loadStream);
                var packageObjectFound = false;
                var name = assembly.ManifestModule.ToString();
                var item = protocolHandlersList.Values.Single(p => p.FileName == name);

                handlerGuid = item.HandlerGuid;

                foreach (Type type in assembly.GetTypes())
                {
                    var interfaces = type.GetInterfaces().AsQueryable().Cast<Type>();

                    if (interfaces.Any(t => t.Name == "IUnknown") && type.HasCustomAttribute<GuidAttribute>())
                    {
                        var guidAttribute = type.GetCustomAttribute<GuidAttribute>();
                        var guid = Guid.Parse(guidAttribute.Value);

                        if (guid == item.HandlerGuid)
                        {
                            ProtocolHandlerAssemblies.Add(guid, assembly);

                            uiThread.Run(() =>
                            {
                                OnProtocolHandlerAssemblyLoaded(guid, assembly);
                            });

                            packageObjectFound = true;
                            break;
                        }
                    }
                }

                if (!packageObjectFound)
                {
                    Debugger.Break();
                }

                if (ProviderClientManager.EnableCaching)
                {
                    if (!item.Cached)
                    {
                        CacheHandlerAssembly(assemblyStream, name, item);
                    }
                }
            }
            catch (Exception ex)
            {
                OnProtocolHandlerAssemblyLoadFailed(handlerGuid, ex);
            }

            loading = false;
        }

        private void CacheHandlerAssembly(MemoryStream assemblyStream, string name, IProtocolHandlerListItem item)
        {
            assemblyStream.Seek(0, SeekOrigin.Begin);

            var savedFile = new ProviderClientManager.SavedFile<ProtocolHandlerListItemLocal>
            {
                FileName = name,
                Extra = new ProtocolHandlerListItemLocal
                {
                    FileName = item.FileName,
                    ModifiedDate = item.ModifiedDate,
                    ID = item.HandlerGuid.ToString()
                },
                Data = assemblyStream.ToArray(),
                LastSaved = DateTime.UtcNow
            };

            // Log.InfoFormat("Package '{0}' being added to database. Save date {1:MM/dd/yyyy hh:mm:ss}", name, savedFile.LastSaved);

            assemblyStream.Close();

            var task = new Task(() =>
            {
                try
                {
                    ProviderClientManager.NameThread("CacheHandlerAssembly");

                    using (var session = DatabaseSession.Open(ProviderClientManager.DATABASE_NAME, ProviderClientManager.Log, OnOpenException))
                    {
                        var fileTable = session.GetTable<ProviderClientManager.SavedFile<ProtocolHandlerListItemLocal>>();

                        if (fileTable.Any(s => s.FileName == savedFile.FileName))
                        {
                            Debugger.Break();
                        }
                        else
                        {
                            fileTable.Add(savedFile);
                        }
                    }
                }
                catch (Exception ex)
                {
                    throw;
                }
            });

            task.Start();
        }

        public override UIObjects.ILog Log
        {
            get
            {
                return hydraUIInternals.Log;
            }
            set
            {
                throw new NotImplementedException();
            }
        }

        public override ItemGenerationOperation GenerateAbstraXItemFromID(string id)
        {
            return this.hydraUIInternals.GenerateAbstraXItemFromID(id);
        }
#endif
    }

    public class HydraPackageListItemLocal : IHydraPackageListItem
    {
        public string FileName { get; set; }
        public DateTime ModifiedDate { get; set; }
        public string ID { get; set; }
        public Guid PackageGuid { get; set; }
        public bool Cached { get; set; }
    }

    public class ProtocolHandlerListItemLocal
    {
        public string FileName { get; set; }
        public DateTime ModifiedDate { get; set; }
        public string ID { get; set; }
    }
}
